#!/bin/sh
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Version information. Please keep this in head of the updater.
TARGET_FWID="REPLACE_FWID"
TARGET_ECID="REPLACE_ECID"
TARGET_PDID="REPLACE_PDID"
TARGET_PLATFORM="REPLACE_PLATFORM"
TARGET_SCRIPT="REPLACE_SCRIPT"
STABLE_FWID="REPLACE_STABLE_FWID"
STABLE_ECID="REPLACE_STABLE_ECID"
STABLE_PDID="REPLACE_STABLE_PDID"

# Export all version information
export TARGET_FWID TARGET_ECID TARGET_PDID TARGET_PLATFORM TARGET_SCRIPT
export STABLE_FWID STABLE_ECID STABLE_PDID
set -e

# Global variables
SELF="$(readlink -f "$0")"

# Set by make_temp function and removed by clean_temp
TMP_DIR=
# Decides if we need to print debug messages
IS_DEBUG=
# The default script to be invoked from extracted bundle
SCRIPT="./${TARGET_SCRIPT:-updater.sh}"

# Tag file to prohibit updater execution
TAG_FILE_DISABLED='/root/.leave_firmware_alone'
# Set to True to bypass checking TAG_FILE_DISABLED
IS_FORCED=

# Set to true to prevent printing error alerts by error return value
IS_IGNORE_RC=

# Prints a message and return an error code ($1).
die_as() {
  local ret="$1"
  shift
  echo "ERROR: $@" >&2
  exit "$ret"
}

# Prints a message and return error as 1.
die() {
  die_as 1 "$@"
}

# Prints messages if $IS_DEBUG is not empty.
debug() {
  [ -z "$IS_DEBUG" ] || echo "$@" >&2
}

# Creates a temporary folder
make_temp() {
  TMP_DIR="$(mktemp -d --tmpdir)" ||
    die "Failed to create temporary folder"
  trap clean_temp EXIT
}

# Creates a temporary folder with execution permission
make_exec_temp() {
  make_temp
  debug "bind and remount for allowing execution ..."
  mount --bind "$TMP_DIR" "$TMP_DIR" &&
    mount -o remount,exec "$TMP_DIR" ||
    die "Failed to enable execution permission of folder $TMP_DIR"
}

# Cleans temporary folders
clean_temp() {
  debug "clean_temp: started."
  if [ -d "$TMP_DIR" ]; then
    umount -f "$TMP_DIR" >/dev/null 2>&1 || true
    rm -rf "$TMP_DIR" >/dev/null 2>&1 || true
    TMP_DIR=""
  fi
}

# Extracts bundle content to specified location
extract_bundle() {
  local destination="$1"
  sh "$SELF" --sb_extract "$destination" >/dev/null ||
    die "Cannot extract bundle content to: $destination"
}

# Executes a script in bundle
exec_bundle_script() {
  local rc=0
  make_exec_temp
  extract_bundle "$TMP_DIR"
  [ -x "$TMP_DIR/$SCRIPT" ] || die "Missing program in bundle: $SCRIPT"

  debug "Start running script: $SCRIPT $@"
  (cd "$TMP_DIR" && "$SCRIPT" "$@") || rc="$?"

  if [ "$rc" -ne 0 -a -z "$IS_IGNORE_RC" ]; then
    die_as "$rc" "Execution failed: $SCRIPT (error code = $rc)"
  fi
  exit "$rc"
}

# Prepares for extraction with shar
prepare_shar_extract() {
  local destination="$1"
  if [ -z "$destination" ]; then
    make_temp
    destination="$TMP_DIR"
    # Don't remove the temporary files
    TMP_DIR=""
  fi
  echo "Extracting to: $destination"
  cd "$destination" || die "Invalid destination: $destination"
  exec >/dev/null  # Prevent shar messages in stdout
  set -- "-c"  # Force shar to overwrite files
}

find_version_string() {
  local filename="$1"
  local pattern="$2"
  if [ ! -s "$filename" ]; then
    return
  fi

  # Chrome OS & chroot has dump_fmap, and standard Linux desktop has strings.
  if type dump_fmap >/dev/null 2>&1; then

    local tmpdir="$(mktemp -d)"
    local filepath="$(readlink -f "$filename")"
    (cd "$tmpdir"; dump_fmap -x "$filepath") >/dev/null 2>&1
    cat "$tmpdir/RO_FRID" 2>/dev/null
    rm -rf "$tmpdir"

  elif type strings >/dev/null 2>&1; then

    local version="$( (strings "$filename" | grep "$pattern" | uniq) || true)"
    if [ "$(echo "version" | wc -l)" -ne "1" ]; then
      version=""
    fi
    echo "$version"

  else

    (echo "WARNING: 'strings' and 'dump_fmap' are both not available."
     echo "         TARGET_{FW,EC,PD}ID can't be updated."
     echo "         You have to manually change that or repack on a desktop."
     ) >&2

  fi
}

# Repacks current file ($SELF) by given source folder.
perform_shar_repack() {
  local new_source="$1"
  local cut_mark="$(sed -n '/^##CUTHERE##/=' "$SELF")"
  local md5_file="$new_source/VERSION.md5"
  local fw_ver="$(find_version_string "$new_source/bios.bin" '^Google_')"
  local ec_ver="$(find_version_string "$new_source/ec.bin" \
                  '^[a-zA-Z0-9]*_v[0-9\.]*-[a-z0-9]*$')"
  local pd_ver="$(find_version_string "$new_source/pd.bin" \
                  '^[a-zA-Z0-9]*_v[0-9\.]*-[a-z0-9]*$')"

  # Since mosys r430, trailing spaces reported by mosys is always scrubbed.
  ec_ver="$(echo "$ec_ver" | sed 's/ *$//')"

  [ "$cut_mark" -gt 0 ] || die "File corrupted: $SELF"
  sed -i "$((cut_mark + 1)),\$d" "$SELF" ||
    die "Failed to truncate existing data in $SELF"

  # Try to update firmware version if available.
  if [ -n "$fw_ver" ]; then
    sed -i 's/^TARGET_FWID=".*"/TARGET_FWID="'"$fw_ver"'"/' "$SELF" &&
      echo "Changed TARGET_FWID to $fw_ver"
    [ -s "$new_source/VERSION" ] &&
      sed -i "s/^BIOS version: .*/BIOS version: $fw_ver/" "$new_source/VERSION"
  fi
  if [ -n "$ec_ver" ]; then
    sed -i 's/^TARGET_ECID=".*"/TARGET_ECID="'"$ec_ver"'"/' "$SELF" &&
      echo "Changed TARGET_ECID to $ec_ver"
    [ -s "$new_source/VERSION" ] &&
      sed -i "s/^EC version:   .*/EC version:   $ec_ver/" "$new_source/VERSION"
  fi
  if [ -n "$pd_ver" ]; then
    sed -i 's/^TARGET_PDID=".*"/TARGET_PDID="'"$pd_ver"'"/' "$SELF" &&
      echo "Changed TARGET_PDID to $pd_ver"
    [ -s "$new_source/VERSION" ] &&
      sed -i "s/^PD version:   .*/PD version:   $pd_ver/" "$new_source/VERSION"
  fi

  # Update checksum data for every files except VERSION*
  (cd "$new_source" &&
   echo "Package Content:" &&
   find . -type f '!' -name VERSION.md5 '!' -name VERSION \
          -exec md5sum -b '{}' '+' ) >"$md5_file"

  # Build shar content
  (cd "$new_source" &&
   shar -Q -q -x -m --no-character-count -D --no-i18n -z -g 1 . |
   sed -r 's/^lock_dir=_sh.*/lock_dir=_fwupdate/;
           s"^begin ([0-9]+) _sh[^/]*"begin \1 _fwupdate"
           /^# Made on .* by/d;
           /^# Source directory was /d') >>"$SELF" ||
   die "Failed repacking from $new_source"
}

# Prints the VAR from '--param VAR' and '--param=VAR' format.
get_parameter_variable() {
  local param="$1"
  local param_name="${param%%=*}"
  local param_value="${param#*=}"

  if [ "$param" = "$param_name" ]; then
    echo "$2"
  else
    echo "$param_value"
  fi
}

# Main entry
main() {
  local original_params="$*"

  case "$1" in
    --sb_extract | --sb_extract=*)
      local destination="$(get_parameter_variable "$@")"
      prepare_shar_extract "$destination"
      return  # Let shar handle the remaining stuff
      ;;

    --sb_repack )
      local new_source="$(get_parameter_variable "$@")"
      [ -d "$new_source" ] || die "Invalid source folder: $new_source"
      echo "Repacking from: $new_source"
      perform_shar_repack "$new_source"
      exit 0
      ;;

    -V)
      # Read information
      make_temp
      extract_bundle "$TMP_DIR"
      cat "$TMP_DIR/VERSION"*
      exit 0
      ;;

    -h | "-?" | --help)
      echo "
USAGE: $SELF [bundle_option|--] [updater_options]

bundle_option (only one option can be selected):
  -h,--help:  Show usage help
  -V:  show version and content of bundle
  --force:  force execution and ignore $TAG_FILE_DISABLED
  --sb_extract [PATH]:  extract bundle content to a temporary folder
  --sb_repack PATH:  update bundle content from given folder

updater_options:
      "
      # Invoke script with -h for usage help
      IS_IGNORE_RC=TRUE
      exec_bundle_script "-h"
      ;;

    --force)
      # Pass this into updaters
      IS_FORCED=TRUE
      ;;

    --debug | --debug | -v)
      # do not shift here because this needs to be passed into the script
      IS_DEBUG=TRUE
      ;;

    --)
      shift
      ;;
  esac

  # Do nothing if the OS specifies that.
  # TODO(hungte) move this flag to kernel command line, or updater bundle
  # itself.
  if [ -e "$TAG_FILE_DISABLED" ] && [ -z "$IS_FORCED" ]; then
    echo "WARNING: $SELF is disabled by $TAG_FILE_DISABLED"
    echo "To force execution, please prefix --force to your command:"
    echo " sudo $SELF --force $original_params"
    exit 0
  fi

  exec_bundle_script "$@"
}

main "$@"

# Below are for shar execution. Don't put any code below main.
##CUTHERE##################################################################
